package com.aliyun.api.internal.mapping;

import com.aliyun.api.AliyunResponse;
import com.googlecode.openbeans.BeanInfo;
import com.googlecode.openbeans.Introspector;
import com.googlecode.openbeans.PropertyDescriptor;
import com.taobao.api.ApiException;
import com.taobao.api.Constants;
import com.taobao.api.internal.mapping.ApiField;
import com.taobao.api.internal.mapping.ApiListField;
import com.taobao.api.internal.mapping.Reader;
import com.taobao.api.internal.util.StringUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 转换工具类。
 *
 * @author lijie.ma
 * @since 1.0, May 19, 2014
 */
public class Converters {

    /**
     * 是否对JSON返回的数据类型进行校验，默认不校验。给内部测试JSON返回时用的开关。
     * 规则：返回的"基本"类型只有String,Long,Boolean,Date,采取严格校验方式，如果类型不匹配，报错
     */
    public static boolean isCheckJsonType = false;

    private static final Set<String> baseFields = new HashSet<String>();
    private static final Map<String, Field> fieldCache = new ConcurrentHashMap<String, Field>();

    static {
        baseFields.add("errorCode");
        baseFields.add("message");
        baseFields.add("body");
        baseFields.add("params");
        baseFields.add("success");
    }

    private Converters() {
    }

    /**
     * 使用指定 的读取器去转换字符串为对象。
     *
     * @param <T>    领域泛型
     * @param clazz  领域类型
     * @param reader 读取器
     *
     * @return 领域对象
     *
     * @throws ApiException
     */
    public static <T> T convert(Class<T> clazz, Reader reader) throws ApiException {
        T rsp = null;

        try {
            rsp = clazz.newInstance();
            BeanInfo beanInfo = Introspector.getBeanInfo(clazz);
            PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();

            for (PropertyDescriptor pd : pds) {
                Method method = pd.getWriteMethod();
                if (method == null) { // ignore read-only fields
                    continue;
                }

                String itemName = pd.getName();
                String listName = null;

                Field field;
                if (baseFields.contains(itemName) && AliyunResponse.class.isAssignableFrom(clazz)) {
                    field = getField(AliyunResponse.class, pd);
                } else {
                    field = getField(clazz, pd);
                }

                ApiField jsonField = field.getAnnotation(ApiField.class);
                if (jsonField != null) {
                    itemName = jsonField.value();
                }
                ApiListField jsonListField = field.getAnnotation(ApiListField.class);
                if (jsonListField != null) {
                    listName = jsonListField.value();
                }

                if (!reader.hasReturnField(itemName)) {
                    if (listName == null || !reader.hasReturnField(listName)) {
                        continue; // ignore non-return field
                    }
                }

                Class<?> typeClass = field.getType();
                // 目前
                if (String.class.isAssignableFrom(typeClass)) {
                    Object value = reader.getPrimitiveObject(itemName);
                    if (value instanceof String) {
                        method.invoke(rsp, value.toString());
                    } else {
                        if (isCheckJsonType && value != null) {
                            throw new ApiException(itemName + " is not a String");
                        }
                        if (value != null) {
                            method.invoke(rsp, value.toString());
                        } else {
                            method.invoke(rsp, "");
                        }
                    }
                } else if (Long.class.isAssignableFrom(typeClass)) {
                    Object value = reader.getPrimitiveObject(itemName);
                    if (value instanceof Long) {
                        method.invoke(rsp, (Long) value);
                    } else {
                        if (isCheckJsonType && value != null) {
                            throw new ApiException(itemName + " is not a Number(Long)");
                        }
                        if (StringUtils.isNumeric(value)) {
                            method.invoke(rsp, Long.valueOf(value.toString()));
                        }
                    }
                } else if (Integer.class.isAssignableFrom(typeClass)) {
                    Object value = reader.getPrimitiveObject(itemName);
                    if (value instanceof Integer) {
                        method.invoke(rsp, (Integer) value);
                    } else {
                        if (isCheckJsonType && value != null) {
                            throw new ApiException(itemName + " is not a Number(Integer)");
                        }
                        if (StringUtils.isNumeric(value)) {
                            method.invoke(rsp, Integer.valueOf(value.toString()));
                        }
                    }
                } else if (Boolean.class.isAssignableFrom(typeClass)) {
                    Object value = reader.getPrimitiveObject(itemName);
                    if (value instanceof Boolean) {
                        method.invoke(rsp, (Boolean) value);
                    } else {
                        if (isCheckJsonType && value != null) {
                            throw new ApiException(itemName + " is not a Boolean");
                        }
                        if (value != null) {
                            method.invoke(rsp, Boolean.valueOf(value.toString()));
                        }
                    }
                } else if (Double.class.isAssignableFrom(typeClass)) {
                    Object value = reader.getPrimitiveObject(itemName);
                    if (value instanceof Double) {
                        method.invoke(rsp, (Double) value);
                    } else {
                        if (isCheckJsonType && value != null) {
                            throw new ApiException(itemName + " is not a Double");
                        }
                    }
                } else if (Number.class.isAssignableFrom(typeClass)) {
                    Object value = reader.getPrimitiveObject(itemName);
                    if (value instanceof Number) {
                        method.invoke(rsp, (Number) value);
                    } else {
                        if (isCheckJsonType && value != null) {
                            throw new ApiException(itemName + " is not a Number");
                        }
                    }
                } else if (Date.class.isAssignableFrom(typeClass)) {
                    DateFormat format = new SimpleDateFormat(Constants.DATE_TIME_FORMAT);
                    format.setTimeZone(TimeZone.getTimeZone(Constants.DATE_TIMEZONE));
                    Object value = reader.getPrimitiveObject(itemName);
                    if (value instanceof String) {
                        method.invoke(rsp, format.parse(value.toString()));
                    }
                } else if (List.class.isAssignableFrom(typeClass)) {
                    Type fieldType = field.getGenericType();
                    if (fieldType instanceof ParameterizedType) {
                        ParameterizedType paramType = (ParameterizedType) fieldType;
                        Type[] genericTypes = paramType.getActualTypeArguments();
                        if (genericTypes != null && genericTypes.length > 0) {
                            if (genericTypes[0] instanceof Class<?>) {
                                Class<?> subType = (Class<?>) genericTypes[0];
                                List<?> listObjs = reader.getListObjects(listName, itemName, subType);
                                if (listObjs != null) {
                                    method.invoke(rsp, listObjs);
                                }
                            }
                        }
                    }
                } else {
                    Object obj = reader.getObject(itemName, typeClass);
                    if (obj != null) {
                        method.invoke(rsp, obj);
                    }
                }
            }
        } catch (Exception e) {
            throw new ApiException(e);
        }

        return rsp;
    }

    public static Field getField(Class<?> clazz, PropertyDescriptor pd) throws Exception {
        String key = new StringBuilder(clazz.getName()).append("_").append(pd.getName()).toString();
        Field field = fieldCache.get(key);
        if (field == null) {// 这个方法不加锁，初始化并发也没关系，无非多put几次
            field = clazz.getDeclaredField(pd.getName());
            fieldCache.put(key, field);
        }
        return field;
    }
}
